#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "Block.h"
#include "Utility.h"

namespace py = pybind11;

void bind_block(py::module_ m);
void bind_utility(py::module_ m);

PYBIND11_MODULE(fat, m) {
    bind_block(m);
    bind_utility(m);
}

void bind_blocks(py::module_ block) {
    py::class_<Blocks>(block, "Blocks")
        .def(py::init<unsigned int>(), py::arg("blockSize") = 4096)
        .def("__len__", &Blocks::len)
        .def("block_size", &Blocks::blockSize)
        .def("number_of_bytes", &Blocks::numberOfBytes)
        .def("append", &Blocks::append)
        .def("remove", &Blocks::remove)
        .def("get", &Blocks::get)
        .def("clear", &Blocks::clear)
        .def("__iter__", [](Blocks &blocks) {
                return py::make_iterator(blocks.begin(), blocks.end());
            }
            , py::keep_alive<0, 1>())
        .def("from_list", [](const std::vector<Block> &blocks) {
                if (blocks.size() > 0) {
                    auto ptr = std::unique_ptr<Blocks>(new Blocks(blocks[0].size()));
                    for (auto item : blocks) { ptr->append(item); }
                    return ptr;
                } else { return std::unique_ptr<Blocks>(new Blocks()); }
            })
    ;
}

void bind_block(py::module_ m) {
    auto block = m.def_submodule("block");

    py::class_<AbstractBlock> absBlock(block, "AbstractBlock");
    absBlock.def(py::init<unsigned int>())
        .def("size", &AbstractBlock::size)
        .def("data", [](AbstractBlock &b) {
                return py::memoryview::from_memory(b.data(), b.size(), false); 
            })
        .def("__repr__", &AbstractBlock::toString)
    ;

    py::class_<Block, py::smart_holder>(block, "Block", absBlock)
        .def_static("from_size", [](unsigned int size){
                return std::unique_ptr<Block>(new Block(size)); 
            }
            , py::arg("size") = 4096)
        .def_static("from_byte", [](int c, unsigned int capacity, unsigned int repeat) {
	            if (c < 0 || c > 255) { throw py::value_error("c is not ascii"); }
	            if (repeat > capacity) { throw py::value_error("repeat > capacity"); }
                return std::unique_ptr<Block>(new Block(c, capacity, repeat)); 
            }
            , py::arg("c"), py::arg("capacity") = 4096, py::arg("repeat") = 1) 
        .def("__repr__", &Block::toString)
        .def("__eq__", [](Block &left, Block &right) {
                if (left.size() != right.size()) { return false; }
                if (left.data() == right.data()) { return true; }
                return (0 == memcmp(left.data(), right.data(), left.size()))? true : false;
            })
    ;

    bind_blocks(block);
}

void bind_utility(py::module_ m) {
    auto utility = m.def_submodule("utility");
    py::class_<BitUtility>(utility, "BitUtility")
        .def_static("bitWidth", &BitUtility::bitWidth)
        .def_static("align_greater_equal", &BitUtility::alignGreaterEqual)
    ;

    py::class_<Bit>(utility, "Bit")
        .def(py::init<unsigned int>())
        .def("bindex", &Bit::bindex)
        .def("value", &Bit::value)
        .def("mask", &Bit::mask)
        .def("clear", &Bit::clear)
        .def("set", &Bit::set)
        .def("is_set", &Bit::isSet)
        .def("is_clear", &Bit::isClear)
    ;
}

